package com.gitee.cui.security.crypto;

import com.gitee.cui.config.SystemConfig;
import com.gitee.cui.util.ByteUtil;
import com.gitee.cui.util.IOUtil;
import lombok.extern.slf4j.Slf4j;

import javax.crypto.Cipher;
import java.security.*;

/**
 * RSA加密算法实现数字签名
 * @author cuiqingqiang
 * @version 1.0
 * @date 2021/11/4 15:50
 **/
@Slf4j
public class RSASignature {

    /**
     * 加密公钥
     */
    private PublicKey publicKey;

    /**
     * 解密私钥
     */
    private PrivateKey privateKey;

    public static void main(String[] args) {
        RSASignature signature = new RSASignature();

        // 测试字符串
        String text = "测试数字加密字符串";
        try {
            signature.publicKey = RSAEncrypt.loadPublicKey();
            signature.privateKey = RSAEncrypt.loadPrivateKey();
            log.info("加密前的字符串：{}", text);
            // 公钥加密
            byte[] bytes = signature.encrypt(signature.publicKey, text.getBytes());
            // 私钥解密
            byte[] result = signature.decrypt(signature.privateKey, bytes);
            log.info("私钥解密的结果为：{}", new String(result));

            // 字符串生成签名
            byte[] rsaSign = signature.rsaSign(text.getBytes(), signature.privateKey);
            // 签名验证
            boolean success = signature.verify(text.getBytes(), rsaSign, signature.publicKey);
            log.info("字符串签名为：{}", ByteUtil.byteToHex(rsaSign));
            log.info("签名验证结果为：{}", success);

            // 文件签名验证
            String resourceFile = IOUtil.getResourcePath("/system.properties");
            byte[] file = ByteUtil.readFileByBytes(resourceFile);
            byte[] sign = signature.rsaSign(file, signature.privateKey);
            log.info("文件签名为：{}", ByteUtil.byteToHex(sign));

            // 保存文件签名到文件
            String signPath = SystemConfig.getKeystoreDir() + "/fileSign.sign";
            ByteUtil.saveFile(sign, signPath);
            // 验证文件签名
            boolean verify = signature.verify(file, sign, signature.publicKey);
            log.info("文件签名验证结果为：{}", verify);

            // 从文件中读取文件签名内容并进行验证
            byte[] read = ByteUtil.readFileByBytes(signPath);
            log.info("从文件中读取的文件签名为：{}", ByteUtil.byteToHex(read));
            boolean isOk = signature.verify(file, read, signature.publicKey);
            log.info("读取文件签名验证结果为：{}", isOk);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * RSA签名验证
     * @param bytes 原始数据
     * @param rsaSign 签名数据
     * @param publicKey 公钥
     * @return 签名验证是否成功
     */
    private boolean verify(byte[] bytes, byte[] rsaSign, PublicKey publicKey) throws SignatureException {
        try {
            Signature signature = Signature.getInstance("SHA512withRSA");
            signature.initVerify(publicKey);
            signature.update(bytes);
            return signature.verify(rsaSign);
        } catch (Exception e) {
            throw new SignatureException("RSA签名验证[content = " + bytes + "; charset = ; signature = " + rsaSign + "]发生异常", e);
        }
    }

    /**
     * 使用RSA加密算法进行数字签名
     * @param bytes 需要签名数据
     * @param privateKey 私钥
     * @return 数字签名
     */
    private byte[] rsaSign(byte[] bytes, PrivateKey privateKey) throws SignatureException {
        try {
            Signature signature = Signature.getInstance("SHA512withRSA");
            signature.initSign(privateKey);
            signature.update(bytes);

            return signature.sign();
        } catch (Exception e) {
            throw new SignatureException("RSAcontent = " + bytes + "; charset = ", e);
        }
    }

    /**
     * 采用私钥进行解密
     * @param privateKey 解密私钥
     * @param bytes 待解密数据
     * @return 解密后数据
     */
    private byte[] decrypt(PrivateKey privateKey, byte[] bytes) throws Exception {
        if (privateKey == null) {
            throw new Exception("解密私钥为空，请设置");
        }
        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(bytes);
    }

    /**
     * 采用公钥进行加密
     * @param publicKey 公钥
     * @param bytes 待加密数据
     * @return 加密数据
     */
    private byte[] encrypt(PublicKey publicKey, byte[] bytes) throws Exception {
        if (publicKey == null) {
            throw new Exception("加密公钥为空，请设置");
        }

        Cipher cipher = Cipher.getInstance("RSA");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return cipher.doFinal(bytes);
    }
}
